﻿using System;
using System.IO;
using System.Linq;
using System.Collections.Generic;
using Microsoft.Kinect;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

namespace BT.Kinect
{
    public class KinectInput : DrawableGameComponent
    {

        public KinectSensor Sensor { get; private set; }
        public bool SensorFound
        {
            get { return Sensor != null && Sensor.IsRunning && Sensor.Status == KinectStatus.Connected; }
        }

        public Skeleton Skeleton { get; private set; }
        public bool PlayerFound
        {
            get { return elapsed < timeout; }
        }

        private readonly TimeSpan timeout = TimeSpan.FromSeconds(0.5);
        private readonly Dictionary<KinectStatus, string> statusMap = new Dictionary<KinectStatus, string>();
        private TimeSpan elapsed;
        private Texture2D background;
        private SpriteBatch spriteBatch;
        private SpriteFont font;

        public KinectInput(Game game)
            : base(game)
        {
            KinectSensor.KinectSensors.StatusChanged += KinectSensors_StatusChanged;

            DiscoverSensor();

            statusMap.Add(KinectStatus.Connected, "");
            statusMap.Add(KinectStatus.DeviceNotGenuine, "Device Not Genuine");
            statusMap.Add(KinectStatus.DeviceNotSupported, "Device Not Supported");
            statusMap.Add(KinectStatus.Disconnected, "Required");
            statusMap.Add(KinectStatus.Error, "Error");
            statusMap.Add(KinectStatus.Initializing, "Initializing...");
            statusMap.Add(KinectStatus.InsufficientBandwidth, "Insufficient Bandwidth");
            statusMap.Add(KinectStatus.NotPowered, "Not Powered");
            statusMap.Add(KinectStatus.NotReady, "Not Ready");

            game.Components.Add(this);
        }

        public override void Initialize()
        {
            spriteBatch = (SpriteBatch) Game.Services.GetService(typeof(SpriteBatch)); 

            base.Initialize();
        }

        protected override void LoadContent()
        {
            background = Game.Content.Load<Texture2D>("kinectbackground");
            font = Game.Content.Load<SpriteFont>("Segoe16");

            base.LoadContent();
        }

        public override void Update(GameTime gameTime)
        {
            if (!SensorFound)
                return;

            using (SkeletonFrame s = Sensor.SkeletonStream.OpenNextFrame(0))
            {
                if (s == null)
                    return;

                elapsed += gameTime.ElapsedGameTime;
                Skeleton = null;

                if (s.SkeletonArrayLength == 0)
                    return;

                Skeleton[] skeletonData = new Skeleton[s.SkeletonArrayLength];
                s.CopySkeletonDataTo(skeletonData);

                for (int i = 0; i < skeletonData.Length; i++)
                {
                    if (skeletonData[i].TrackingState != SkeletonTrackingState.Tracked)
                        continue;

                    if (Skeleton == null || Skeleton.Position.Z > skeletonData[i].Position.Z)
                        Skeleton = skeletonData[i];

                }

                if (Skeleton != null)
                    elapsed = TimeSpan.FromSeconds(0);
            }

            base.Update(gameTime);
        }


        public override void Draw(GameTime gameTime)
        {
            if (!SensorFound || !PlayerFound)
            {
                string message = "Required";

                if (Sensor != null)
                    message = statusMap[Sensor.Status];

                if (message == "")
                    message = "Player Not Found";

                Vector2 size = font.MeasureString(message);

                int screenWidth = Game.GraphicsDevice.Viewport.Width;
                int screenHeight = Game.GraphicsDevice.Viewport.Height;

                spriteBatch.Begin();

                spriteBatch.Draw(background, new Vector2(screenWidth / 2, screenHeight / 2), null, Color.White,
                    0, new Vector2(background.Width / 2, background.Height / 2), 1, SpriteEffects.None, 0);

                spriteBatch.DrawString(font, message,
                    new Vector2((screenWidth - size.X) / 2, screenHeight / 2 + size.Y), Color.White);

                spriteBatch.End();
            }

            base.Draw(gameTime);
        }

        protected override void UnloadContent()
        {
            if (Sensor != null)
                Sensor.Stop();

            base.UnloadContent();
        }

        private void DiscoverSensor()
        {
            Sensor = KinectSensor.KinectSensors.FirstOrDefault();
            if (Sensor == null || Sensor.Status != KinectStatus.Connected)
                return;

            TransformSmoothParameters parameters = new TransformSmoothParameters();
            parameters.Smoothing = 0.7f;
            parameters.Correction = 0.3f;
            parameters.Prediction = 0.4f;
            parameters.JitterRadius = 1.0f;
            parameters.MaxDeviationRadius = 0.5f;

            Sensor.SkeletonStream.Enable(parameters);
            try
            {
                Sensor.Start();
            }
            catch (IOException)
            {
                Sensor = null;
            }
        }

        private void KinectSensors_StatusChanged(object sender, StatusChangedEventArgs e)
        {
            if (e.Status != KinectStatus.Connected)
                e.Sensor.Stop();

            DiscoverSensor();
        }

    }
}
